<?php
/**
 *  The properties are stored in the __dict__:
 *  - to get a model's property, simple offsetGet() is OK
 *  - to iterate over properties, iterate over the model
 */
abstract class Yz_Model_Abstract
    extends Yz_Class
    implements Yz_Model_Interface
{

    /**
     *  The source table specification
     *  %A => application namespace
     *  %M => module name
     *  %N => model namespace
     */
    const SOURCE_NAME = '%A_%M_%N';

    /** The loaded models (avoid adding $instance to each model class)
     *  @var array (className: model)
     */
    private static $instances = array();

    /** Reference to the module
     *  @var Yz_Module_Interface
     */
    protected $module;

    /**
     *  @var Yz_Config_Interface
     */
    protected $config;

    /** The name of the model
     *  @var string
     */
    protected $name;

    /** The class' reflection object
     *  @var string|null
     */
    protected $reflection;


    /****************************
     *      Public methods      *
     ****************************/


    /**
     *  Model's per-class singleton
     */
    public static function getInstance()
    {
        debug();
        $myclass = get_called_class();
        $i =& self::$instances;
        if (isset($i[$myclass]))
            $obj = $i[$myclass];
        else {
            // separation of assignment is required:
            // otherwise code in init() can not rely on the model's instance being in $instances
            $i[$myclass] = $obj = new static();
            $obj->init();
        }
        return $obj;
    }


    /** Model's constructor
     */
    protected function __construct()
    {
        debug();
        $moduleClass = $this->_ns_() . '\\Module';
        $this->module = $moduleClass::getInstance();
        $this->name = $this->_name_();
    }

    /** Model's string representation
     *  @return string
     */
    public function __toString()
    {
        $moduleName = $this->module->getFullName();
        return "Model({$moduleName}.{$this->name})";
    }

    /** get Model's name
     *  @return string
     */
    final public function getName()
    {
        return $this->name;
    }

    /** get Model's module
     *  @return string
     */
    final public function getModule()
    {
        return $this->module;
    }


    /** get Model's source name
     *  @return string
     */
    public function getSourceName()
    {
        debug();
        $sName =& $this->sourceName;
        if (! $sName) {
            $const = static::SOURCE_NAME;
            $class = $this;
            while (false === $const) {
                $class = get_parent_class($class);
                $const = $class::SOURCE_NAME;
            }
            $sName = strtolower(strtr($const,array(
                '%A' => Yz::getName(), // application namespace
                '%M' => $this->module->getName(), // module name
                '%N' => $class::_name_(), // model name
            )));
        }
        return $sName;
    }

    /**
     *  @return Yz_Model_Interface
     */
    public function selectModel($modelName)
    {
        debug();
        if ($modelName === $this->name) {
            $model = $this;
        } else {
            $model = $this->module->selectModel($modelName);
        }

        return $model;
    }


    /**
     *  identify and create action
     *  @param string $actionName
     *  @return Yz_Action_Interface
     *  @todo map action name => some other name, for model-specific actions
     */
    public function selectAction( $actionName )
    {
        debug();
        assert('is_string($actionName) and "" !== $actionName');
        $me = $this->myReflection();
        $const = 'ACTION_' . strtoupper($actionName);
        if ($me->hasConstant($const))
            $actionName = $me->getConstant($const);
        $class = $this->module->resolveClass('Action_' . $actionName, 'Yz_Action_' . $actionName);
        assert('is_subclass_of($class,"Yz_Action_Interface")');

        // TODO take class name from property of $this
        return new $class($this);
    }

    /********************************
     *      Protected methods       *
     ********************************/


    /**
     *
     */
    final protected function myReflection()
    {
        $r =& $this->reflection;
        $r or $r = new ReflectionClass($this);
        return $r;
    }



    /** the default setter for properties which don't have theirs own
     *  @param string index
     *  @return NEVER
     *  @throw AttributeError
     */
    protected function _set_( $index, $value )
    {
        throw AttributeError::_new_(array('attr' => $index));
    }

    /** the default unsetter for properties which don't have theirs own
     *  @param string index
     *  @return NEVER
     *  @throw AttributeError
     */
    protected function _del_( $index )
    {
        throw AttributeError::_new_(array('attr' => $index));
    }


    protected function init()
    {
        return $this;
    }


}
